﻿function painter(options) {
	var p = {};

	var canvas = document.getElementById(options.canvas);
	var ctx = canvas.getContext('2d');

	var firefox27 = bowser.firefox && bowser.version >= 27 && !bowser.mobile && !bowser.tablet;

	var isTouch = Modernizr !== 'undefined' && Modernizr.touch && !firefox27;

	var move = isTouch ? 'touchmove' :'mousemove',
		down = isTouch ? 'touchstart' : 'mousedown',
		up = isTouch ? 'touchend' : 'mouseup',
		leave = isTouch ? 'touchleave' : 'mouseleave';

	// Создание темпового канваса
	var tmpCanvas = document.createElement('canvas');
	var tmpCtx = tmpCanvas.getContext('2d');
	tmpCanvas.id = 'tmpCanvas';
	tmpCanvas.width = canvas.width;
	tmpCanvas.height = canvas.height;
	// Вставляем темповый канвас поверх существующего
	canvas.parentNode.appendChild(tmpCanvas);

	// Стандартные настройки рисования
	tmpCtx.lineWidth = 6;
	tmpCtx.lineJoin = 'round';
	tmpCtx.lineCap = 'round';
	tmpCtx.strokeStyle = 'black';
	tmpCtx.fillStyle = 'black';

	// положение мыши
	var mouse = { x: 0, y: 0 };

	// положение канваса на странице
	var pos = { left: 0, top: 0 };

	// коллекция точек
	var ppts = [];

	//readonlyMode
	var readOnly = true;

	// Доступ к елементам извне
	p.actions = [];
	p.canvas = canvas;
	p.ctx = ctx;
	p.drawing = false;
	p.tmpCanvas = tmpCanvas;
	p.tmpCtx = tmpCtx;

	// true во время рисования
	var drawing = false;

	// вкл/выкл readonlyMode
	p.readOnly = function (val) {
		if (typeof val === 'boolean') {
			readOnly = val;
		}
		return readOnly;
	};

	// Установить цвет
	p.setColor = function (color) {
		if (readOnly) return;
		setColor(color);
		p.tmpCanvas.focus();
		actionPush('c', color);
	};

	// Установить размер
	p.setBrushSize = function (size) {
		if (readOnly) return;
		setSize(size);
		actionPush('s', size);
	};

	// Очистить
	p.clear = function () {
		if (readOnly) return;
		clear();
		p.tmpCanvas.focus();
		actionPush('clear');
	};


	// ### События ##########################################
	// событие перемещения мыши
	addCanvasEvent(move, function (e) {
		e.preventDefault();
		if (readOnly || !drawing) return;
		updateMouse(e);
	});

	// событие начала рисования
	addCanvasEvent(down, function (e) {
		if (readOnly) return;
		drawing = true;
		addCanvasEvent(move, draw);

		updateCanvasPosition();

		updateMouse(e);

		ppts.push({ x: mouse.x, y: mouse.y });
		actionPush('m', mouse.x, mouse.y);
		onPaint();
	});

	// событие отпустили мышь
	addCanvasEvent(up, drawStop);

	// событие мышь покинула пределы канваса
	addCanvasEvent(leave, drawStop);

	// добавить событие в темповый канвас
	function addCanvasEvent(event, fn) {
		tmpCanvas.addEventListener(event, fn, false);
	}

	// ### Приватные методы #############################################
	// Получение позиции елемента (канваса) на странице
	function updateCanvasPosition() {
		var tmp = canvas;
		var left = tmp.offsetLeft;
		var top = tmp.offsetTop;
		while (tmp = tmp.offsetParent) left += tmp.offsetLeft;
		tmp = canvas;
		while (tmp = tmp.offsetParent) top += tmp.offsetTop;
		pos.left = left;
		pos.top = top;
	}
	
	// Обновление координат мыши
	function updateMouse(e){
		if (isTouch) {
			var touchEvent = e.changedTouches[0];
			mouse.x = touchEvent.pageX - pos.left;
			mouse.y = touchEvent.pageY - pos.top;
		} else {
			mouse.x = typeof e.offsetX !== 'undefined' ? e.offsetX : e.layerX;
			mouse.y = typeof e.offsetY !== 'undefined' ? e.offsetY : e.layerY;
		}
	}
	
	// Логирование действий при рисовании
	function actionPush(/*arguments*/) {
		var action = Array.prototype.slice.call(arguments).join("-");
		setTimeout(function () {
			p.actions.push(action);
			if (p.actions.length >= 100 || (action == 'clear')) {
				sendData();
			}
		}, 0);
	}

	// Отправка коллекции екшинов
	function sendData() {
		setTimeout(function () {
			typeof p.onsendactions !== 'undefined' ? p.onsendactions() : function () { };
			p.actions = [];
		}, 0);
	}
	
	// остановить рисование
	function drawStop() {
		if (!drawing) return;
		drawing = false;
		tmpCanvas.removeEventListener(move, draw, false);
		// Writing down to real canvas now
		ctx.drawImage(tmpCanvas, 0, 0);
		// Clearing tmp canvas
		tmpCtx.clearRect(0, 0, tmpCanvas.width, tmpCanvas.height);
		// Emptying up Pencil Points
		ppts = [];
		sendData();
	}

	// рисование
	function draw(e) {
		e.preventDefault();
		// Saving all the points in an array
		if (readOnly) return;
		var x = mouse.x, y = mouse.y;

		if (drawing &&
			mouse.x < 0 ||
			mouse.y < 0 ||
			mouse.x > canvas.width ||
			mouse.y > canvas.height)
		{
			drawStop();
			return;
		}

		ppts.push({ x: x, y: y });
		actionPush('l', x, y);
		onPaint();
	}

	// само рисование
	function onPaint() {

		if (ppts.length == 0) return;

		if (ppts.length <= 2) {
			var p = ppts[0];
			tmpCtx.beginPath();
			tmpCtx.arc(p.x, p.y, tmpCtx.lineWidth / 2, 0, Math.PI * 2, true);
			tmpCtx.fill();
			tmpCtx.closePath();
			return;
		}

		// Темповый канвас очищается после каждого рисования
		tmpCtx.clearRect(0, 0, tmpCanvas.width, tmpCanvas.height);

		tmpCtx.beginPath();
		tmpCtx.moveTo(ppts[0].x, ppts[0].y);

		for (var i = 1, l = ppts.length - 2; i < l; i++) {
			var c = (ppts[i].x + ppts[i + 1].x) / 2;
			var d = (ppts[i].y + ppts[i + 1].y) / 2;

			tmpCtx.quadraticCurveTo(ppts[i].x, ppts[i].y, c, d);
		}

		// Для 2-х последних точек
		tmpCtx.quadraticCurveTo(
			ppts[i].x,
			ppts[i].y,
			ppts[i + 1].x,
			ppts[i + 1].y
		);

		tmpCtx.stroke();
	};

	// ### Отрисовка изображения по полученным данным ##########################################################################
	p.setData = function () {
		p.drawing = true;
		function drawAction(actionStr) {
			if (typeof actionStr !== 'undefined') {

				function switchCase() {
					var action = actionStr.split('-');
					switch (action[0]) {
						case 'clear':
							clear();
							break;
						case 's':
							setSize(action[1]);
							break;
						case 'c':
							setColor(action[1]);
							break;
						case 'm':
							moveTo(parseInt(action[1]), parseInt(action[2]));
							break;
						case 'l':
							lineTo(parseInt(action[1]), parseInt(action[2]));
							break;
					}
					drawAction(p.actions.shift());
				}

				setTimeout(switchCase, 0);
			}
			else {
				endDraw();
				p.drawing = false;
			}
		}
		//First call drawAction
		drawAction(p.actions.shift());
	};

	function setSize(size) {
		tmpCtx.lineWidth = size;
	}

	function setColor(color) {
		tmpCtx.strokeStyle = color;
		tmpCtx.fillStyle = color;
	}

	function moveTo(x, y) {
		endDraw();
		lineTo(x, y);
	}

	function lineTo(x, y) {
		ppts.push({ x: x, y: y });
		onPaint();
	}

	function clear() {
		tmpCtx.clearRect(0, 0, tmpCanvas.width, tmpCanvas.height);
		ctx.fillStyle = 'white';
		ctx.fillRect(0, 0, canvas.width, canvas.height);
		ctx.fillStyle = tmpCtx.fillStyle;
		ppts = [];
	}

	function endDraw() {
		ctx.drawImage(tmpCanvas, 0, 0);
		tmpCtx.clearRect(0, 0, tmpCanvas.width, tmpCanvas.height);
		ppts = [];
	}

	// ########################################################################################################

	return p;
}